#include "preHeader.h"
#include "Spell.h"
#include "SpellMgr.h"
#include "SpellEffects.h"
#include "Map/MapInstance.h"

uint64 Spell::m_spellInstGuidSeed = 0;

Spell::Spell(
	Unit* pCaster, LocatableObject* pTarget, uint64 spellInstGuid,
	const SpellPrototype* pSpellProto, uint32 spellLevel,
	const vector3f& tgtEffPos, const std::string_view& args)
: m_pCaster(pCaster)
, m_targetGuid(pTarget != NULL ? pTarget->GetGuid() : ObjGUID_NULL)
, m_spellInstGuid(spellInstGuid != 0 ? spellInstGuid : NewSpellInstGuid())
, m_pSpellProto(pSpellProto)
, m_pSpellLevelProto(GetSpellLevelProto(pSpellProto, spellLevel))
, m_spellLevel(spellLevel)
, m_tgtEffPos(tgtEffPos)
, m_args(args)
, m_spellStage(SpellStageType::Chant)
, m_refCounter(0)
, m_isStoped(false)
, m_tgtEffNewPos(pTarget != NULL ? pTarget->GetPosition() : tgtEffPos)
{
	auto sleiNum = m_pSpellLevelProto->sleiList.size();
	m_effectTableList.resize(sleiNum);
	m_effectTargetList.resize(sleiNum);
}

Spell::~Spell()
{
}

WheelTimerMgr *Spell::GetWheelTimerMgr()
{
	return &m_pCaster->GetMapInstance()->sWheelTimerMgr;
}

void Spell::Delete(Spell* pSpell)
{
	delete pSpell;
}

void Spell::Interrupt(SpellInterruptBy interruptType)
{
	m_refCounter = 0;
	RemoveTimers();
	Stop();
}

GErrorCode Spell::ApplySpell(Unit* pCaster,
	Unit* pTarget, uint64 spellInstGuid, uint32 spellID,
	uint32 spellLevel, int32 effectIndexes, bool isInAura)
{
	auto pSpellProto = sSpellMgr.GetSpellPrototype(spellID);
	if (pSpellProto == NULL || pSpellProto->sliList.empty() ||
		pSpellProto->sliList.size() < spellLevel) {
		return ErrSpellInvalidParam;
	}

	Spell spellInst(pCaster, pTarget, spellInstGuid,
		pSpellProto, spellLevel, vector3f_INVALID, emptyStringView);
	auto pSpellLevelProto = GetSpellLevelProto(pSpellProto, spellLevel);
	for (size_t i = 0, n = pSpellLevelProto->sleiList.size(); i < n; ++i) {
		auto sleiInfo = pSpellLevelProto->sleiList[i].sleiInfo;
		if (sleiInfo->spellEffectType == (u32)SpellEffectType::None) {
			continue;
		}
		if ((effectIndexes >= 0 && effectIndexes != s32(i)) &&
			(effectIndexes < 0 && !BIT_ISSET(-effectIndexes, s32(i)))) {
			continue;
		}
		spellInst.LoadEffectInfoTable(i, *sleiInfo);
		auto& effectRefTable = spellInst.GetEffectTable(i);
		if (!effectRefTable.isref()) {
			continue;
		}
		auto effectTable = effectRefTable.Get<LuaTable>();
		if (isInAura) {
			effectTable.get<LuaTable>("t1").set("duration", 0);
		}
		LuaFuncs(effectTable).CallStaticMethod<void>("ApplyEffect", pTarget);
	}

	return CommonSuccess;
}

GErrorCode Spell::ResumeSpell(
	Unit* pReceiver, uint32 spellID, uint32 spellLevel,
	uint32 effectIndex, uint64 elapseTime, uint64 skipTime,
	const std::string_view& buffArgs)
{
	auto pSpellProto = sSpellMgr.GetSpellPrototype(spellID);
	if (pSpellProto == NULL || pSpellProto->sliList.empty() ||
		pSpellProto->sliList.size() < spellLevel) {
		return ErrSpellInvalidParam;
	}
	auto pSpellLevelProto = GetSpellLevelProto(pSpellProto, spellLevel);
	if (pSpellLevelProto->sleiList.size() <= effectIndex) {
		return ErrSpellInvalidParam;
	}
	auto sleiInfo = pSpellLevelProto->sleiList[effectIndex].sleiInfo;
	if (sleiInfo->spellEffectType == (u32)SpellEffectType::None) {
		return ErrSpellInvalidParam;
	}
	auto errCode = CanCastSpell4Position(pReceiver, pSpellProto, spellLevel);
	if (errCode != CommonSuccess) {
		return errCode;
	}

	switch (SpellResumeType(pSpellProto->siInfo->spellBuffType)) {
	case SpellResumeType::RemoveWhenOffline:
		elapseTime = INT64_MAX / 2;
		break;
	case SpellResumeType::ContinueWhenOnline:
		skipTime = 0;
		break;
	case SpellResumeType::ContinueByTime:
		break;
	}

	Spell spellInst(pReceiver, NULL, 0,
		pSpellProto, spellLevel, vector3f_INVALID, emptyStringView);
	spellInst.LoadEffectInfoTable(effectIndex, *sleiInfo);
	auto& effectRefTable = spellInst.GetEffectTable(effectIndex);
	if (!effectRefTable.isref()) {
		return ErrSpellInternalError;
	}

	auto effectTable = effectRefTable.Get<LuaTable>();
	auto buffBaseTable = effectTable.get<LuaTable>("t1");
	buffBaseTable.set("start", s64(GET_SYS_TIME - elapseTime - skipTime));
	buffBaseTable.set("elapse", elapseTime);
	if (skipTime != 0) {
		buffBaseTable.set("skip", skipTime);
	}
	if (!buffArgs.empty()) {
		buffBaseTable.set("args", buffArgs);
	}

	LuaFuncs(effectTable).CallStaticMethod<void>("ApplyEffect", pReceiver);

	return CommonSuccess;
}

GErrorCode Spell::CanCastSpell(
	Unit* pCaster, LocatableObject* pTarget, int spellCastFlags,
	const SpellPrototype* pSpellProto, uint32 spellLevel,
	const vector3f& tgtEffPos, const std::string_view& args)
{
	if (!pCaster->IsInWorld()) {
		return ErrSpellNotInWorld;
	}
	if (pSpellProto == NULL || pSpellProto->sliList.empty() ||
		pSpellProto->sliList.size() < spellLevel) {
		return ErrSpellInvalidParam;
	}
	auto siInfo = pSpellProto->siInfo;
	if (siInfo->spellFlags.isExclusive) {
		auto errCode = pCaster->CanGrabFgSpell(pSpellProto, spellLevel);
		if (errCode != CommonSuccess) {
			return errCode;
		}
	}
	auto errCode = pCaster->Cooldown_CanCast(pSpellProto);
	if (errCode != CommonSuccess) {
		return errCode;
	}
	if (pCaster->IsType(TYPE_PLAYER)) {
		auto pPlayer = static_cast<Player*>(pCaster);
		if (siInfo->spellLimitCareer != 0) {
			if (siInfo->spellLimitCareer != pPlayer->GetCareer()) {
				return ErrSpellInvalidCaster;
			}
		}
	}
	auto pSpellLevelProto = GetSpellLevelProto(pSpellProto, spellLevel);
	if (pSpellLevelProto->sliInfo->spellLimitLevel > pCaster->GetLevel()) {
		return ErrSpellNotEnoughLevel;
	}
	errCode = CanCastSpell4Effects(pTarget, pCaster, pSpellProto, spellLevel);
	if (errCode != CommonSuccess) {
		return errCode;
	}
	errCode = CanCastSpell4Target(pTarget, pCaster, pSpellProto, spellLevel);
	if (errCode != CommonSuccess) {
		return errCode;
	}
	errCode = CanCastSpell4HP(pCaster, pSpellLevelProto);
	if (errCode != CommonSuccess) {
		return errCode;
	}
	errCode = CanCastSpell4MP(pCaster, pSpellLevelProto);
	if (errCode != CommonSuccess) {
		return errCode;
	}
	errCode = CanCastSpell4Position(pCaster, pSpellProto, spellLevel);
	if (errCode != CommonSuccess) {
		return errCode;
	}
	if (siInfo->spellLimitScriptID != 0) {
		auto errCode = CanCastSpell4Script(pCaster, pSpellProto, spellLevel);
		if (errCode != CommonSuccess) {
			return errCode;
		}
	}
	if ((spellCastFlags & (int)CanCastSpellFlag::CheckDistance) != 0) {
		if (pSpellLevelProto->sliInfo->spellCastDistMin != 0) {
			if (SQ(pSpellLevelProto->sliInfo->spellCastDistMin) >
				(pTarget != NULL ? pCaster->GetDistanceSq(pTarget) : -FLT_MAX)) {
				return ErrSpellInvalidDist;
			}
		}
		if (pSpellLevelProto->sliInfo->spellCastDistMax != 0) {
			if (SQ(pSpellLevelProto->sliInfo->spellCastDistMax) <
				(pTarget != NULL ? pCaster->GetDistanceSq(pTarget) : FLT_MAX)) {
				return ErrSpellInvalidDist;
			}
		}
	}
	return CommonSuccess;
}

void Spell::CastSpell(
	Unit* pCaster, LocatableObject* pTarget, uint64 spellInstGuid,
	const SpellPrototype* pSpellProto, uint32 spellLevel,
	const vector3f& tgtEffPos, const std::string_view& args)
{
	Spell* pSpell = new Spell(pCaster, pTarget,
		spellInstGuid, pSpellProto, spellLevel, tgtEffPos, args);
	pCaster->OnStartSpell(pSpell);
	pSpell->StartSpell4Effects();
	pSpell->Chant();
}

void Spell::Chant()
{
	if (IsNeedSync2Client()) {
		SendSyncChant();
	}

	LoadAllEffectInfoTables();

	m_spellStage = SpellStageType::Chant;
	ActivateSpellEffects(SpellStageType::Chant);

	auto animTime = GetCastStageTime(SpellStageType::Chant);
	if (animTime != 0) {
		CreateTimer(std::bind(&Spell::Cast, this),
			TimerTypeSpellChantStage, animTime, 1);
	} else {
		Cast();
	}
}

void Spell::Cast()
{
	CastSpell4HP();
	CastSpell4MP();

	AddCooldown();

	Channel();
}

void Spell::Channel()
{
	if (IsNeedSync2Client()) {
		SendSyncChannel();
	}

	m_spellStage = SpellStageType::Channel;
	ActivateSpellEffects(SpellStageType::Channel);

	auto animTime = GetCastStageTime(SpellStageType::Channel);
	if (animTime != 0) {
		CreateTimer(std::bind(&Spell::Cleanup, this),
			TimerTypeSpellChannelStage, animTime, 1);
	} else {
		Cleanup();
	}
}

void Spell::Cleanup()
{
	if (IsNeedSync2Client()) {
		SendSyncCleanup();
	}

	m_spellStage = SpellStageType::Cleanup;
	ActivateSpellEffects(SpellStageType::Cleanup);

	auto animTime = GetCastStageTime(SpellStageType::Cleanup);
	if (animTime != 0) {
		CreateTimer(std::bind(&Spell::Stop, this),
			TimerTypeSpellCleanupStage, animTime, 1);
	} else {
		Stop();
	}
}

void Spell::Stop()
{
	if (!m_isStoped && IsNeedSync2Client()) {
		SendSyncStop();
	}

	if (!m_isStoped) {
		m_isStoped = true;
		this->StopSpell4Effects();
		m_pCaster->OnStopSpell(this);
	} else {
		m_pCaster->OnReleaseSpell(this);
	}
}

void Spell::SendSyncChant() const
{
	NetPacket pack(SMSG_SPELL_SYNC_CHANT);
	pack << m_pCaster->GetGuid()
		<< m_pCaster->GetPosition() << m_pCaster->GetDirection();
	pack << m_spellInstGuid
		<< m_pSpellProto->siInfo->spellID << m_spellLevel;
	SendSyncPacket(pack);
}

void Spell::SendSyncChannel() const
{
	NetPacket pack(SMSG_SPELL_SYNC_CHANNEL);
	pack << m_pCaster->GetGuid()
		<< m_pCaster->GetPosition() << m_pCaster->GetDirection();
	pack << m_spellInstGuid
		<< m_pSpellProto->siInfo->spellID << m_spellLevel;
	SendSyncPacket(pack);
}

void Spell::SendSyncCleanup() const
{
	NetPacket pack(SMSG_SPELL_SYNC_CLEANUP);
	pack << m_pCaster->GetGuid()
		<< m_pCaster->GetPosition() << m_pCaster->GetDirection();
	pack << m_spellInstGuid
		<< m_pSpellProto->siInfo->spellID << m_spellLevel;
	SendSyncPacket(pack);
}

void Spell::SendSyncStop() const
{
	NetPacket pack(SMSG_SPELL_SYNC_STOP);
	pack << m_pCaster->GetGuid()
		<< m_pCaster->GetPosition() << m_pCaster->GetDirection();
	pack << m_spellInstGuid
		<< m_pSpellProto->siInfo->spellID << m_spellLevel;
	SendSyncPacket(pack);
}

void Spell::SendSyncPacket(const INetPacket& pck) const
{
	m_pCaster->PushMessage(pck, IsNeedSync2AllClient());
}

uint32 Spell::GetCastStageTime(SpellStageType stage) const
{
	uint32 animTime = 0;
	auto siInfo = m_pSpellProto->siInfo;
	if ((int)stage >= 0 && (u8)stage < siInfo->spellStageTime.size()) {
		animTime = siInfo->spellStageTime[(u8)stage];
	}
	return animTime + GetCastStageExtraTime(stage);
}

uint32 Spell::GetCastStageExtraTime(SpellStageType stage) const
{
	uint32 extraAnimTime = 0;
	const auto& sleiList = m_pSpellLevelProto->sleiList;
	for (size_t i = 0, n = sleiList.size(); i < n; ++i) {
		auto sleiInfo = sleiList[i].sleiInfo;
		switch ((SpellEffectType)sleiInfo->spellEffectType) {
		case SpellEffectType::Mine:
			extraAnimTime = std::max(extraAnimTime,
				SpellEffects::GetCastStageExtraTime4Mine(this, stage, i));
			break;
		}
	}
	return extraAnimTime;
}

void Spell::LoadAllEffectInfoTables()
{
	const auto& sleiList = m_pSpellLevelProto->sleiList;
	for (size_t i = 0, n = sleiList.size(); i < n; ++i) {
		auto sleiInfo = sleiList[i].sleiInfo;
		if (sleiInfo->spellEffectType == (u32)SpellEffectType::None) {
			continue;
		}
		if (sleiInfo->spellSelectType == (u8)SpellSelectType::None) {
			continue;
		}
		if (sleiInfo->spellEffectFlags.isClientSelectTarget) {
			continue;
		}
		LoadEffectInfoTable(i, *sleiInfo);
	}
}

void Spell::LoadEffectInfoTable(size_t i, const SpellLevelEffectInfo& sleiInfo)
{
	char buffer[PATH_MAX];
	snprintf(buffer, sizeof(buffer),
		"scripts/Effect/%u.lua", 1000 + sleiInfo.spellEffectType);
	auto L = m_pCaster->GetMapInstance()->L;
	RunScriptFile(L, buffer, this, i, CalcUniqueKey4EffectArgs(i),
		std::string_view(sleiInfo.spellEffectArgs), std::string_view(m_args));
}

void Spell::SaveEffectTable(size_t i, LuaTable t, LuaTable t1)
{
	m_effectTableList[i] = LuaRef(t.getL(), t.index());
	if (t1.is_alive()) {
		t.set<const LuaTable&>("t1", t1);
	}
}

void Spell::SaveEffectInstInfos(size_t i, LuaTable t)
{
	t.set("spell", this);
	t.set("obj", m_pCaster);
	t.set("index", i);
}

void Spell::SaveEffectBuffInfos(size_t i, LuaTable t)
{
	t.set("caster", m_pCaster);
	t.set("guid", m_spellInstGuid);
	t.set("proto", m_pSpellProto);
	t.set("level", m_spellLevel);
	t.set("index", i);
}

void Spell::ActivateSpellEffects(SpellStageType stage)
{
	const auto& sleiList = m_pSpellLevelProto->sleiList;
	for (size_t i = 0, n = sleiList.size(); i < n; ++i) {
		auto sleiInfo = sleiList[i].sleiInfo;
		if (sleiInfo->spellStageTrigger != (u8)stage) {
			continue;
		}
		if (sleiInfo->spellSelectType == (u8)SpellSelectType::Reference) {
			continue;
		}
		if (!m_effectTableList[i].isref()) {
			continue;
		}
		m_refCounter += 1;
		if (sleiInfo->spellDelayTrigger != 0) {
			this->CreateTimer(
				std::bind(&Spell::StartSelectEffectTargets, this, i, std::ref(*sleiInfo)),
				TimerTypeSpellDelayTrigger + (u32)i, sleiInfo->spellDelayTrigger, 1);
		} else {
			StartSelectEffectTargets(i, *sleiInfo);
		}
	}
}

void Spell::StartReferenceSpellEffects(size_t tgtSleiIdx)
{
	const auto& sleiList = m_pSpellLevelProto->sleiList;
	for (size_t i = 0, n = sleiList.size(); i < n; ++i) {
		auto sleiInfo = sleiList[i].sleiInfo;
		if (sleiInfo->spellSelectType != (u8)SpellSelectType::Reference) {
			continue;
		}
		if (sleiInfo->spellSelectArgs[0] != tgtSleiIdx) {
			continue;
		}
		if (!m_effectTableList[i].isref()) {
			continue;
		}
		m_refCounter += 1;
		m_effectTargetList[i] = m_effectTargetList[tgtSleiIdx];
		StartSelectEffectTargets(i, *sleiInfo);
	}
}

void Spell::StartSelectEffectTargets(size_t i, const SpellLevelEffectInfo& sleiInfo)
{
	if (sleiInfo.spellSelectType < (u8)SpellSelectType::Count) {
		if (m_spellTargetTypeSelectors[sleiInfo.spellSelectType] != NULL) {
			(this->*m_spellTargetTypeSelectors[sleiInfo.spellSelectType])
				(m_effectTargetList[i], sleiInfo);
		}
	}

	if (sleiInfo.spellDelayEffective != 0) {
		this->CreateTimer(
			std::bind(&Spell::StartApplyEffect2Targets, this, i, std::ref(sleiInfo)),
			TimerTypeSpellDelayEffective + (u32)i, sleiInfo.spellDelayEffective, 1);
	} else {
		StartApplyEffect2Targets(i, sleiInfo);
	}

	StartReferenceSpellEffects(i);
}

void Spell::StartApplyEffect2Targets(size_t i, const SpellLevelEffectInfo& sleiInfo)
{
	m_refCounter -= 1;
	if (m_isStoped && m_refCounter == 0) {
		Stop();
	}

	LuaTable effectTable(m_effectTableList[i].Get<LuaTable>());
	auto funcApplyEffect = effectTable.get<LuaFunc>("ApplyEffect");
	auto buffBaseTable = effectTable.get<LuaTable>("t1");
	if (buffBaseTable.is_alive()) {
		buffBaseTable.set("start", GET_SYS_TIME);
	}

	auto pMapInstance = m_pCaster->GetMapInstance();
	for (auto targetGuid : m_effectTargetList[i]) {
		auto pTarget = pMapInstance->GetUnit(targetGuid);
		if (pTarget == NULL) {
			continue;
		}
		if (pTarget->IsDead() &&
			!sleiInfo.spellEffectFlags.isTargetDeadable) {
			continue;
		}
		if (sleiInfo.spellEffectiveChance != .0f &&
			System::Randf(0, 1) > sleiInfo.spellEffectiveChance) {
			continue;
		}
		funcApplyEffect.Call<void>(pTarget);
	}
}

void Spell::AddCooldown()
{
	m_pCaster->Cooldown_Add(m_pSpellProto, m_spellLevel);
}

uint64 Spell::CalcUniqueKey4EffectArgs(size_t i) const
{
	return ((u64)m_pSpellProto->siInfo->spellID << 32) +
		(std::max(m_spellLevel, 1u) << 16) + i;
}

void Spell::CastSpell4HP()
{
	auto sliInfo = m_pSpellLevelProto->sliInfo;
	auto needHPValue = sliInfo->spellCostHP +
		u64(m_pCaster->GetS64Value(UNIT64_FIELD_HP_MAX) * sliInfo->spellCostHPRate);
	m_pCaster->SubHPValue(needHPValue);
}

void Spell::CastSpell4MP()
{
	auto sliInfo = m_pSpellLevelProto->sliInfo;
	auto needMPValue = sliInfo->spellCostMP +
		u64(m_pCaster->GetS64Value(UNIT64_FIELD_MP_MAX) * sliInfo->spellCostMPRate);
	m_pCaster->SubMPValue(needMPValue);
}

GErrorCode Spell::CanCastSpell4HP(
	Unit* pCaster, const SpellLevelPrototype* pSpellLevelProto)
{
	auto sliInfo = pSpellLevelProto->sliInfo;
	auto needHPValue = sliInfo->spellCostHP +
		u64(pCaster->GetS64Value(UNIT64_FIELD_HP_MAX) * sliInfo->spellCostHPRate);
	if (pCaster->GetS64Value(UNIT64_FIELD_HP) <= sig(needHPValue)) {
		return ErrSpellNotEnoughHP;
	}
	return CommonSuccess;
}

GErrorCode Spell::CanCastSpell4MP(
	Unit* pCaster, const SpellLevelPrototype* pSpellLevelProto)
{
	auto sliInfo = pSpellLevelProto->sliInfo;
	auto needMPValue = sliInfo->spellCostMP +
		u64(pCaster->GetS64Value(UNIT64_FIELD_MP_MAX) * sliInfo->spellCostMPRate);
	if (pCaster->GetS64Value(UNIT64_FIELD_MP) < sig(needMPValue)) {
		return ErrSpellNotEnoughMP;
	}
	return CommonSuccess;
}

GErrorCode Spell::CanCastSpell4Position(
	Unit* pCaster, const SpellPrototype* pSpellProto, uint32 spellLevel)
{
	auto siInfo = pSpellProto->siInfo;
	if (siInfo->spellLimitMapType != 0) {
		if (siInfo->spellLimitMapType != pCaster->GetMapType()) {
			return ErrSpellInvalidPosition;
		}
	}
	if (siInfo->spellLimitMapID != 0) {
		if (siInfo->spellLimitMapID != pCaster->GetMapId()) {
			return ErrSpellInvalidPosition;
		}
	}
	return CommonSuccess;
}

GErrorCode Spell::CanCastSpell4Script(
	Unit* pCaster, const SpellPrototype* pSpellProto, uint32 spellLevel)
{
	auto L = pCaster->GetMapInstance()->L;
	auto siInfo = pSpellProto->siInfo;
	const auto& scriptFile = GetScriptFileById(siInfo->spellLimitScriptID);
	if (!scriptFile.empty() && sLuaMgr.DoFile(L, scriptFile)) {
		return LuaFunc(L, "CanCastSpell").Call<GErrorCode>(pCaster,
			pSpellProto, spellLevel, std::string_view(siInfo->spellLimitScriptArgs));
	}
	return CommonSuccess;
}

GErrorCode Spell::CanCastSpell4Target(LocatableObject* pTarget,
	Unit* pCaster, const SpellPrototype* pSpellProto, uint32 spellLevel)
{
	switch (SpellTargetType(pSpellProto->siInfo->spellTargetType)) {
	case SpellTargetType::Friend:
		if (pTarget == NULL || !pTarget->IsKindOf(TYPE_UNIT)) {
			return ErrSpellInvalidTarget;
		}
		if (!pCaster->IsFriend((Unit*)pTarget)) {
			return ErrSpellInvalidTarget;
		}
		break;
	case SpellTargetType::Enemy:
		if (pTarget == NULL || !pTarget->IsKindOf(TYPE_UNIT)) {
			return ErrSpellInvalidTarget;
		}
		if (!pCaster->IsHostile((Unit*)pTarget)) {
			return ErrSpellInvalidTarget;
		}
		break;
	case SpellTargetType::TeamMember:
		if (!pCaster->IsType(TYPE_PLAYER)) {
			return ErrSpellInvalidCaster;
		}
		if (pTarget == NULL || !pTarget->IsKindOf(TYPE_PLAYER)) {
			return ErrSpellInvalidTarget;
		}
		if (!((Player*)pCaster)->IsPlayTeamMember(pTarget->GetGuid())) {
			return ErrSpellInvalidTarget;
		}
	}
	if (!pSpellProto->siInfo->spellFlags.isTargetDeadable) {
		if (pTarget != NULL && pTarget->IsKindOf(TYPE_UNIT)) {
			if (((Unit*)pTarget)->IsDead()) {
				return ErrSpellInvalidTarget;
			}
		}
	}
	if (pSpellProto->siInfo->spellFlags.isCheckCastDist) {
		if (pTarget != NULL) {
			auto distSQ = pCaster->GetDistanceSq(pTarget);
			auto pSpellLevelProto = GetSpellLevelProto(pSpellProto, spellLevel);
			if (pSpellLevelProto->sliInfo->spellCastDistMin != .0f &&
				SQ(pSpellLevelProto->sliInfo->spellCastDistMin) > distSQ) {
				return ErrSpellInvalidTarget;
			}
			if (pSpellLevelProto->sliInfo->spellCastDistMax != .0f &&
				SQ(pSpellLevelProto->sliInfo->spellCastDistMax) < distSQ) {
				return ErrSpellInvalidDist;
			}
		}
	}
	return CommonSuccess;
}

GErrorCode Spell::CanCastSpell4Effects(LocatableObject* pTarget,
	Unit* pCaster, const SpellPrototype* pSpellProto, uint32 spellLevel)
{
	GErrorCode errCode = CommonSuccess;
	auto pSpellLevelProto = GetSpellLevelProto(pSpellProto, spellLevel);
	for (size_t i = 0, n = pSpellLevelProto->sleiList.size(); i < n; ++i) {
		auto& sleiInfo = *pSpellLevelProto->sleiList[i].sleiInfo;
		switch (SpellEffectType(sleiInfo.spellEffectType)) {
		case SpellEffectType::Mine:
			errCode = SpellEffects::
				CanCast4Mine(pCaster, pTarget, pSpellProto, spellLevel, i);
			break;
		}
		if (errCode != CommonSuccess) {
			break;
		}
	}
	return errCode;
}

void Spell::StartSpell4Effects()
{
	for (size_t i = 0, n = m_pSpellLevelProto->sleiList.size(); i < n; ++i) {
		auto& sleiInfo = *m_pSpellLevelProto->sleiList[i].sleiInfo;
		switch (SpellEffectType(sleiInfo.spellEffectType)) {
		case SpellEffectType::Mine:
			SpellEffects::Start4Mine(this, i);
			break;
		}
	}
}

void Spell::StopSpell4Effects()
{
	for (size_t i = 0, n = m_pSpellLevelProto->sleiList.size(); i < n; ++i) {
		auto& sleiInfo = *m_pSpellLevelProto->sleiList[i].sleiInfo;
		switch (SpellEffectType(sleiInfo.spellEffectType)) {
		case SpellEffectType::Mine:
			SpellEffects::Stop4Mine(this, i);
			break;
		}
	}
}
